package com.abel.bigwater.data.sanchuan

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.Bit32Array
import com.abel.bigwater.model.JsonDateSerializer
import com.abel.bigwater.model.JsonHelper
import com.alibaba.fastjson.annotation.JSONField
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import java.util.*
import kotlin.experimental.and

class IntensiveDataPart(_dataLen: Int?) : SanchuanData(_dataLen) {

    /**
     * 1 密集采样数据组数 上传组数，默认 48，该值表示本次将上传多少组累计流量增量。
     */
    var count: Short? = null

    /**
     * 密集采样流量指示
     */
    var flag: IntensiveSampleType? = null

    /**
     * 5 密集采样起始时间 5 字节（采用 BCD 码），年月日时分。 禁止上报密集采样流量时仍上报密集采样起始时间
     */
    var startTimeStr: BcdString? = null

    @JsonSerialize(using = JsonDateSerializer::class)
    @JSONField(format = JsonHelper.FULL_DATE_FORMAT)
    var startTime: Date? = null
        get() {
            field = if (startTimeStr == null) null
            else startTimeStr!!.toStartTime()!!.toDate()
            return field
        }
        set(value) {
            field = value
            startTimeStr =
                    if (field == null) null
                    else BcdString.fromStartTime(DateTime(field))
        }

    /**
     * 4*N 密集采样正向流量 1 若间隔时间单位是 5 分钟，当前时间为 7 月 3 日 16 点 46 分。若密集采样起始时间为 8 点 0 分，
     * 密集采样逆向流量 1 则当前上报的数据为 7 月 3 日 8 点 0 分至 11 点 55 分的密集采样流量。密集采样流量 1 为 8 点 0
    分至 8 点 5 分的数据，密集采样流量 2 为 8 点 5 分至 8 点 10 分的数据，依次类推。
    若密集采样起始时间为 16 点 0 分，则当前上报的数据为 7 月 2 日 16 点 0 分至 19 点 55 分的密集
    采样流量。
    …
    密集采样正向流量 N
    密集采样逆向流量 N
     */
    var forwardSumStrList: ArrayList<BcdString>? = null

    var revertSumStrList: ArrayList<BcdString>? = null

    /**
     * 累计流量增量, unit: m³
     */
    var sumList: ArrayList<IntensiveSumRow>? = null
        get() {
            assert(startTimeStr!!.size == 5)

            field = ArrayList()
            for (idx in 0..(count!! - 1)) {
                field!!.add(IntensiveSumRow().apply {
                    sampleTime = DateTime(startTimeStr!!.toSanchunDate()).plusMinutes(idx * flag!!.samplePeriod.minutes).toDate()
                    endTime = DateTime(sampleTime).plusMinutes(flag!!.samplePeriod.minutes).toDate()

                    val forwardLong = forwardSumStrList!![idx].toUlongLEHex(2)!!
                    forward = if (forwardLong == 0xFFFFL) null else forwardLong * 0.001F

                    val revertLong = revertSumStrList!![idx].toUlongLEHex(2)!!
                    revert = if (revertLong == 0x7FFFL) null else revertLong * 0.001F
                })
            }

            return field
        }


    /**
     * 计算数据长度，针对解析和打包
     */
    override fun computeDataLen(): Int {
        dataLen = 1 + 1 + 5 + 4 * count!!
        return dataLen!!
    }

    /**
     * 从当前位置写入打包字节流
     */
    override fun fillBuffer(buf: ByteBuf) {
        computeDataLen()

        buf.alloc().buffer().apply {
            writeByte(count!!.toInt())

            writeByte(flag!!.back)

            writeBytes(startTimeStr!!.hexArray)

            for (idx in 0..(count!! - 1)) {
                assert(2 == forwardSumStrList!![idx].size)
                assert(2 == revertSumStrList!![idx].size)

                writeBytes(forwardSumStrList!![idx].hexArray)
                writeBytes(revertSumStrList!![idx].hexArray)
            }

            // transfer to buf.
            buf.writeBytes(this)
        }
    }

    /**
     * 解析字节流，从buf当前位置开始，并且消费其中的字节。
     */
    override fun <T : SanchuanData> parseBuffer(buf: ByteBuf): T? {
        buf.duplicate().apply {
            count = readByte().toShort().and(0xFF)

            flag = IntensiveSampleType(readByte().toInt())

            startTimeStr = BcdString.create(this, 5)
        }
        computeDataLen()

        buf.readBytes(dataLen!!).apply {
            skipBytes(1 + 1 + 5)

            for (idx in 0..(count!! - 1)) {
                forwardSumStrList!!.add(BcdString.create(this, 2))
                revertSumStrList!!.add(BcdString.create(this, 2))
            }
        }

        return this as T
    }

}

/**
 * 1 密集采样流量指示
BIT0 密集采样流量上报使能 0 禁止上报密集采样流量 1 允许上报密集采样流量
BIT1~3 密集采样流量格式
0 格式 0，每组 3 字节，单位 0.001L，例： 3 字节依次为 0xff,0x00,0x00 则表示密集采
样流量为 0.255L。 (0xFFFFFF 表示没有该值)
1 格式 1，每组 2 字节，单位 1L，例： 2 字节依次为 0xff,0x00 则表示密集采样流量为
255L。 (0xFFFF 表示没有该值)
2 格式 2，每组 1 字节，单位 0.1 立方米，例： 0x0f 表示密集采样流量为 1.5 立方米(0xFF
表示没有该值)
3 格式 3，每组 2 字节，单位 1L，例： 2 字节依次为 0xff,0x00 则表示密集采样流量为
255L。 (48 组数据为密集累计流量，而不是正向流量+逆向流量， 0x7FFF
表示没有该值)
其它 保留
BIT4~6 密集采样间隔单位
0 5 分钟
1 10 分钟
2 20 分钟
3 30 分钟
4 1 小时
5 2 小时
6 3 小时
7 4 小时
BIT7 保留
 */
class IntensiveSampleType(v1: Int) : Bit32Array(v1) {
    /**
     * 1 密集采样流量指示
    BIT0 密集采样流量上报使能 0 禁止上报密集采样流量 1 允许上报密集采样流量
     */
    var sampleFlag: Boolean
        get() = get(0) == 1
        set(value) {
            set(0, if (value) 1 else 0)
        }

    /**
     *
    BIT1~3 密集采样流量格式
    0 格式 0，每组 3 字节，单位 0.001L，例： 3 字节依次为 0xff,0x00,0x00 则表示密集采
    样流量为 0.255L。 (0xFFFFFF 表示没有该值)
    1 格式 1，每组 2 字节，单位 1L，例： 2 字节依次为 0xff,0x00 则表示密集采样流量为
    255L。 (0xFFFF 表示没有该值)
    2 格式 2，每组 1 字节，单位 0.1 立方米，例： 0x0f 表示密集采样流量为 1.5 立方米(0xFF
    表示没有该值)
    3 格式 3，每组 2 字节，单位 1L，例： 2 字节依次为 0xff,0x00 则表示密集采样流量为
    255L。 (48 组数据为密集累计流量，而不是正向流量+逆向流量， 0x7FFF
    表示没有该值)
    其它 保留
     */
    var sampleFormat: Int
        get() = get(1, 3)
        set(value) {
            set(1, 3, value)
        }

    /**
     *
    BIT4~6 密集采样间隔单位
    0 5 分钟
    1 10 分钟
    2 20 分钟
    3 30 分钟
    4 1 小时
    5 2 小时
    6 3 小时
    7 4 小时
    BIT7 保留
     */
    var samplePeriod: SamplePeriodType
        get() = SamplePeriodType.findByCode(get(4, 6))
        set(value) {
            set(4, 6, value.code)
        }
}

enum class SamplePeriodType {
    Five(0, 5),
    Ten(1, 10),
    Twenty(2, 20),
    Half(3, 30),
    Hour(4, 60),
    TwoHour(5, 120),
    ThreeHour(6, 180),
    FourHour(7, 240);

    val code: Int

    val minutes: Int

    constructor(_code: Int, _minutes: Int) {
        code = _code
        minutes = _minutes
    }

    companion object {
        fun findByCode(_code: Int): SamplePeriodType {
            return SamplePeriodType.values().find { it.code == _code }!!
        }
    }
}
